#!/bin/bash
#
# script: rc.docker
#
# Short-Description:  Create lightweight, portable, self-sufficient containers.
# Description:
# Docker is an open-source project to easily create lightweight, portable,
# self-sufficient containers from any application. The same container that a
# developer builds and tests on a laptop can run at scale, in production, on
# VMs, bare metal, OpenStack clusters, public clouds and more.
#
# LimeTech - modified for Unraid OS
# Bergware - modified for Unraid OS, January 2025

DAEMON="Docker daemon"
UNSHARE="/usr/bin/unshare"
SYSTEM="/sys/class/net"
CONF6="/proc/sys/net/ipv6/conf"
STOCK="bond br eth wlan"
ACTIVE=$(ls --indicator-style=none $SYSTEM | awk "/^(${STOCK// /|})[0-9]/" ORS=' ')
NICS=$(ls --indicator-style=none $SYSTEM | awk '/^(eth|wlan)[0-9]+$/')

DOCKERD="dockerd"
DOCKER="/usr/bin/$DOCKERD"
DOCKER_PIDFILE="/var/run/$DOCKERD.pid"
DOCKER_LOG="/var/log/docker.log"
DOCKER_ROOT="/var/lib/docker"
DOCKER_CFG="/boot/config/docker.cfg"
DOCKER_TIMEOUT=$(awk -F'"' '/^DOCKER_TIMEOUT=/{print $2}' $DOCKER_CFG 2>/dev/null)

# network file references
INI=/var/local/emhttp/network.ini
TMP=/var/tmp/network.tmp

# run & log functions
. /etc/rc.d/rc.runlog

carrier(){
  cat $SYSTEM/$1/carrier 2>/dev/null
}

# determine active port name
[[ -e $SYSTEM/bond0 ]] && PORT=bond0 || PORT=eth0
[[ -e $SYSTEM/br0 ]] && PORT=br0
[[ $(carrier $PORT) != 1 && -e $SYSTEM/wlan0 && $(carrier wlan0) == 1 ]] && PORT=wlan0

# Set defaults used by the docker daemon
if [[ -f $DOCKER_CFG ]]; then
  for NIC in $NICS; do
    if [[ ${NIC:0:3} == eth ]]; then
      if [[ -e $SYSTEM/${NIC/eth/br} ]]; then
        NIC=${NIC/eth/br}
      elif [[ -e $SYSTEM/${NIC/eth/bond} ]]; then
        NIC=${NIC/eth/bond}
      fi
    fi
    CFG=($(grep -Pom2 "_SUBNET_|_${NIC^^}(_[0-9]+)?=" $DOCKER_CFG))
    if [[ ${CFG[0]} == _SUBNET_ && -z ${CFG[1]} ]]; then
      # interface has changed, update configuration
      X=${NIC//[^0-9]/}
      sed -ri "s/_(BR|BOND|ETH|WLAN)$X(_[0-9]+)?=/_${NIC^^}\2=/; s/(br|bond|eth|wlan)$X(\.[0-9]+)? /$NIC\2 /g" $DOCKER_CFG
    fi
  done
  # Read (updated) Unraid docker configuration file
  . $DOCKER_CFG
fi

# set storage driver to overlay2 if config value is found, otherwise fall back to native FS driver
if [[ $(awk -F'"' '/^DOCKER_BACKINGFS=/{print $2}' $DOCKER_CFG 2>/dev/null) == overlay2 ]]; then
  DOCKER_OPTS="$DOCKER_OPTS --storage-driver=overlay2"
else
  BACKINGFS=$(findmnt --output FSTYPE --noheadings $DOCKER_ROOT)
  if [[ $BACKINGFS == btrfs ]]; then
    DOCKER_OPTS="$DOCKER_OPTS --storage-driver=btrfs"
  elif [[ $BACKINGFS == xfs ]]; then
    DOCKER_OPTS="$DOCKER_OPTS --storage-driver=overlay2"
  elif [[ $BACKINGFS == zfs ]]; then
    DOCKER_OPTS="$DOCKER_OPTS --storage-driver=zfs"
  fi
fi

# Less verbose logging by default
DOCKER_OPTS="--log-level=fatal $DOCKER_OPTS"

# Enable global docker LOG rotation
if [[ $DOCKER_LOG_ROTATION == yes ]]; then
  [[ -z $DOCKER_LOG_SIZE ]] && DOCKER_LOG_SIZE=10m
  [[ -z $DOCKER_LOG_FILES ]] && DOCKER_LOG_FILES=1
  DOCKER_OPTS="--log-opt max-size=$DOCKER_LOG_SIZE --log-opt max-file=$DOCKER_LOG_FILES $DOCKER_OPTS"
fi

# Adjust MTU size if non-default
MTU=$(ip link show $PORT | grep -Po 'mtu \K\d+')
[[ -n $MTU && $MTU -ne 1500 ]] && DOCKER_OPTS="--mtu=$MTU $DOCKER_OPTS"

# Enable IPv6 for docker bridge network
if [[ -n $(ip -6 route show default dev $PORT) ]]; then
  DOCKER0='fd17::/64'
  DOCKER_OPTS="--ipv6 --fixed-cidr-v6=$DOCKER0 $DOCKER_OPTS"
  IPV6_FORWARD=${IPV6_FORWARD:=accept}
  # create IPv6 NAT rule for docker0
  [[ -z $(ip6tables -t nat -S | grep -o "$DOCKER0") ]] && run ip6tables -t nat -A POSTROUTING -s $DOCKER0 ! -o docker0 -j MASQUERADE
else
  # ipv6 disabled
  [[ -d $CONF6/docker0 ]] && echo 1 > $CONF6/docker0/disable_ipv6
fi

export DOCKER_RAMDISK=true

# Get docker daemon PID (if existing)
docker_pid(){
  cat $DOCKER_PIDFILE 2>/dev/null
}

# Verify if docker daemon running
docker_running(){
  sleep 0.1
  [[ -S /var/run/docker.sock ]] || return 1
  [[ $(docker info 2>&1) =~ "Cannot connect to the Docker daemon" ]] && return 1 || return 0
}

# Wait max 30s to daemon start
wait_daemon(){
  for n in {1..30}; do
    if docker_running; then return 0; else sleep 1; fi
  done
  return 1
}

# All existing containers
all_containers(){
  docker ps -a --format='{{.Names}}' 2>/dev/null
}

# Running containers
running_containers(){
  docker ps --format='{{.Names}} {{.Labels}}' 2>/dev/null | grep 'net.unraid.docker.managed=' | awk '{print $1}'
}

# Network driver
driver(){
  # user selection when bridge is enabled
  if [[ -z $DOCKER_NETWORK_TYPE ]]; then
    DETACH='ipvlan'
    ATTACH='macvlan'
    MODE='bridge'
  else
    DETACH='macvlan'
    ATTACH='ipvlan'
    MODE='l2 bridge'
  fi
  # fixed selection when bridge is disabled
  if [[ $1 != br ]]; then
    DETACH='ipvlan'
    ATTACH='macvlan'
    MODE='bridge'
  fi
}

# Custom networks
network(){
  docker network ls --filter driver="$1" --format='{{.Name}}' 2>/dev/null | grep -P "^[a-z]+$2(\$|\.)" | tr '\n' ' '
}

# Is container running?
container_running(){
  local CONTAINER
  for CONTAINER in $(running_containers); do
    [[ $CONTAINER == $1 ]] && return 0
  done
  return 1
}

# Does the container exist?
container_exist(){
  local CONTAINER
  for CONTAINER in $(all_containers); do
    [[ $CONTAINER == $1 ]] && return 0
  done
  return 1
}

container_paths_exist(){
  local CONTAINER=$1
  while IFS=| read -r HOSTPATH; do
    # Make sure hostpath exists
    if [[ ! -e "$HOSTPATH" ]]; then
      log "container \"$CONTAINER\" hostpath \"$HOSTPATH\" does not exist"
      return 1
    fi
  done <<< $(docker inspect --format='{{range .Mounts}}{{.Source}}|{{end}}' $CONTAINER)
  return 0
}

read_dom(){
  local IFS=\>
  read -d \< ENTITY CONTENT
}

container_add_route(){
  local CT=($(docker inspect --format='{{.State.Pid}} {{.NetworkSettings.Networks}}' $1))
  local PID=${CT[0]}
  local NET=${CT[1]#*[}
  local LAN=${NET%:*}
  if [[ $PID -gt 0 && "eth0 br0 bond0 wlan0" =~ $LAN ]]; then
    local THISIP=$(ip -4 -br addr show dev $LAN scope global | awk '{print $3;exit}')
    for CFG in /etc/wireguard/wg*.cfg ; do
      local NETWORK=$(ip -4 show route dev $LAN $THISIP | awk '{print $1;exit}')
      [[ -n $NETWORK ]] && nsenter -n -t $PID ip -4 route add $NETWORK via ${THISIP%/*} dev $LAN 2>/dev/null
    done
  fi
}

docker_network_start(){
  log "Starting network..."
  # create list of possible custom networks
  EXCLUDE=; INCLUDE=$(ls --indicator-style=none $SYSTEM | awk '/^br[0-9]+/' ORS=' ')
  while IFS=$'\n' read -r NETWORK; do
    if [[ ${NETWORK:0:4} == bond ]]; then
      if [[ $INCLUDE =~ "${NETWORK/bond/br} " ]]; then
        EXCLUDE="${EXCLUDE}${NETWORK} ${NETWORK/bond/eth} "
      else
        INCLUDE="${INCLUDE}${NETWORK} "
        EXCLUDE="${EXCLUDE}${NETWORK/bond/eth} "
      fi
    elif [[ ${NETWORK:0:3} == eth ]]; then
      if [[ $INCLUDE =~ "${NETWORK/eth/br} " || $INCLUDE =~ "${NETWORK/eth/bond} " ]]; then
        [[ $EXCLUDE =~ "$NETWORK " ]] || EXCLUDE="${EXCLUDE}${NETWORK} "
      else
        INCLUDE="${INCLUDE}${NETWORK} "
      fi
    else
      INCLUDE="${INCLUDE}${NETWORK} "
    fi
  done <<< $(ls --indicator-style=none $SYSTEM | grep -P '^(bond|eth|wlan)[0-9]+')
  if ! docker_running; then return 1; fi
  # get container settings for custom networks to reconnect later
  declare -A NETRESTORE CTRESTORE
  CONTAINERS=$(docker container ls -a --format='{{.Names}}' | tr '\n' ' ')
  for CONTAINER in $CONTAINERS; do
    # the file case (due to fat32) might be different so use find to match
    XMLFILE=$(find /boot/config/plugins/dockerMan/templates-user -maxdepth 1 -iname my-${CONTAINER}.xml)
    if [[ -n $XMLFILE ]]; then
      REBUILD=
      MAIN=
      # update custom network reference (if changed)
      for NIC in $NICS; do
        if [[ ${NIC:0:3} == eth ]]; then
          if [[ -e $SYSTEM/${NIC/eth/br} ]]; then
            NIC=${NIC/eth/br}
          elif [[ -e $SYSTEM/${NIC/eth/bond} ]]; then
            NIC=${NIC/eth/bond}
          fi
        fi
        X=${NIC//[^0-9]/}
        REF=$(grep -Pom1 "<Network>\K(br|bond|eth|wlan)$X" $XMLFILE)
        [[ $X == 0 && $(carrier $NIC) != 1 ]] && continue
        [[ $X == 0 && $NIC != wlan0 ]] && MAIN=$NIC
        [[ $NIC == wlan0 && -n $MAIN ]] && continue
        if [[ -n $REF && $REF != $NIC ]]; then
          sed -ri "s/<Network>(br|bond|eth|wlan)$X(\.[0-9]+)?<\/Network>/<Network>$NIC\2<\/Network>/" $XMLFILE
          # flag container for later rebuild
          REBUILD=1
        fi
      done
      MY_NETWORK= MY_IP=
      while read_dom; do
        [[ $ENTITY == Network ]] && MY_NETWORK=$CONTENT
        [[ $ENTITY == MyIP ]] && MY_IP=${CONTENT// /,} && MY_IP=$(echo "$MY_IP" | tr -s "," ";")
      done <$XMLFILE
      # only restore valid networks
      if [[ -n $MY_NETWORK ]]; then
        NETRESTORE[$MY_NETWORK]="$CONTAINER,$MY_IP ${NETRESTORE[$MY_NETWORK]}"
        # save container name for later rebuild
        CTRESTORE[$MY_NETWORK]=$REBUILD
      fi
    fi
    # restore user defined networks
    USER_NETWORKS=$(docker inspect --format='{{range $key,$value:=.NetworkSettings.Networks}}{{$key}};{{if $value.IPAMConfig}}{{if $value.IPAMConfig.IPv4Address}}{{$value.IPAMConfig.IPv4Address}}{{end}}{{if $value.IPAMConfig.IPv6Address}},{{$value.IPAMConfig.IPv6Address}}{{end}}{{end}} {{end}}' $CONTAINER)
    for ROW in $USER_NETWORKS; do
      ROW=(${ROW/;/ })
      USER_NETWORK=${ROW[0]}
      USER_IP=${ROW[1]/,/;}
      if [[ -n $USER_NETWORK && $USER_NETWORK != $MY_NETWORK ]]; then
        LABEL=${USER_NETWORK//[0-9.]/}
        IF_NO_PARTS=${USER_NETWORK#"$LABEL"}
        IF_NO=${IF_NO_PARTS%%.*}
        if [[ $STOCK =~ $LABEL && $IF_NO -gt 0 ]]; then
          USER_NETWORK=$USER_NETWORK
        elif [[ $STOCK =~ $LABEL && $LABEL != ${PORT:0:-1} ]]; then
          USER_NETWORK=${USER_NETWORK/$LABEL/${PORT:0:-1}}
        fi
        log "container $CONTAINER has an additional network that will be restored: $USER_NETWORK"
        NETRESTORE[$USER_NETWORK]="$CONTAINER,$USER_IP ${NETRESTORE[$USER_NETWORK]}"
      fi
    done
  done
  # detach custom networks
  for NIC in $NICS; do
    if [[ ${NIC:0:3} == eth ]]; then
      if [[ -e $SYSTEM/${NIC/eth/br} ]]; then
        NIC=${NIC/eth/br}
      elif [[ -e $SYSTEM/${NIC/eth/bond} ]]; then
        NIC=${NIC/eth/bond}
      fi
    fi
    X=${NIC//[^0-9]/}
    driver ${NIC//[0-9]/}
    for NETWORK in $(network $DETACH $X); do
      [[ $STOCK =~ ${NETWORK%%[0-9]*} || $DOCKER_USER_NETWORKS != preserve ]] && docker network rm $NETWORK >/dev/null
    done
    # get existing custom networks
    for NETWORK in $(network $ATTACH $X); do
      if [[ $STOCK =~ ${NETWORK%%[0-9]*} ]]; then
        [[ $EXCLUDE =~ "$NETWORK " || ! $ACTIVE =~ "$NETWORK " ]] && docker network rm $NETWORK >/dev/null
      else
        [[ $DOCKER_USER_NETWORKS != preserve ]] && docker network rm $NETWORK >/dev/null
      fi
    done
    NETWORKS=$(network $ATTACH $X)
  done
  # add or remove custom network
  for NETWORK in $INCLUDE; do
    if [[ ! $DOCKER_CUSTOM_NETWORKS =~ "$NETWORK " ]]; then
      # automatic assignment
      AUTO=${NETWORK/./_}
      AUTO=DOCKER_AUTO_${AUTO^^}
      if [[ ${!AUTO} == no ]]; then
        [[ $NETWORKS =~ "$NETWORK " ]] && docker network rm $NETWORK >/dev/null
        continue
      fi
      # add auto defined networks
      SUBNET=; GATEWAY=; SERVER=; RANGE=;
      [[ -z ${!AUTO} || ${!AUTO} =~ "4" ]] && IPV4=$(ip -4 -br addr show $NETWORK scope global | awk '{print $3;exit}') || IPV4=
      if [[ -n $IPV4 ]]; then
        SUBNET=$(ip -4 route show $IPV4 dev $NETWORK | awk '{print $1;exit}')
        SERVER=${IPV4%/*}
        DHCP=${NETWORK/./_}
        DHCP=DOCKER_DHCP_${DHCP^^}
        RANGE=${!DHCP}
        GATEWAY=$(ip -4 route show default dev $NETWORK | awk '{print $3;exit}')
      fi
      SUBNET6=; GATEWAY6=; SERVER6=;
      [[ -z ${!AUTO} || ${!AUTO} =~ "6" ]] && IPV6=$(ip -6 -br addr show $NETWORK scope global -temporary -deprecated | awk '{print $3;exit}') || IPV6=
      if [[ -n $IPV6 ]]; then
        # get IPV6 subnet, preset to /64 if single host address is given
        [[ ${IPV6#*/} == 128 ]] && SUBNET6=$(echo $IPV6 | sed -r 's/^([^:]+):([^:]+):([^:]+):([^:]+).*$/\1:\2:\3:\4::\/64/') || SUBNET6=$(ip -6 route show $IPV6 dev $NETWORK | awk '{print $1;exit}')
        SERVER6=${IPV6%/*}
        GATEWAY6=$(ip -6 route show default dev $NETWORK | awk '{print $3;exit}')
        # replace link local address for first address in subnet
        [[ ${GATEWAY6:0:4} == fe80 ]] && GATEWAY6=${SUBNET6%/*}1
      fi
    else
      # add user defined networks
      IPV4=; IPV6=;
      DEVICE=${NETWORK/./_}
      DEVICE=${DEVICE^^}
      SUBNET=DOCKER_SUBNET_$DEVICE
      SUBNET=${!SUBNET}
      GATEWAY=DOCKER_GATEWAY_$DEVICE
      GATEWAY=${!GATEWAY}
      SERVER=;
      RANGE=DOCKER_RANGE_$DEVICE
      RANGE=${!RANGE}
      SUBNET6=DOCKER_SUBNET6_$DEVICE
      SUBNET6=${!SUBNET6}
      GATEWAY6=DOCKER_GATEWAY6_$DEVICE
      GATEWAY6=${!GATEWAY6}
      SERVER6=;
    fi
    # custom network already existing and changed?
    if [[ $NETWORKS =~ "$NETWORK " ]]; then
      UPDATE=;
      SUBNETS=($(docker network inspect --format='{{range .IPAM.Config}}{{.Subnet}} {{end}}' $NETWORK 2>/dev/null))
      RANGES=($(docker network inspect --format='{{range .IPAM.Config}}{{.IPRange}} {{end}}' $NETWORK 2>/dev/null))
      GATEWAYS=($(docker network inspect --format='{{range .IPAM.Config}}{{.Gateway}} {{end}}' $NETWORK 2>/dev/null))
      SERVERS=($(docker network inspect --format='{{range .IPAM.Config}}{{range $IPAddr := .AuxiliaryAddresses}}{{$IPAddr}}{{end}} {{end}}' $NETWORK 2>/dev/null))
      # distribute ipv4 and ipv6 assignments
      [[ ${SUBNETS[0]} =~ '.' ]] && SUBNET0=${SUBNETS[0]} || SUBNET1=${SUBNETS[0]}
      [[ -n ${SUBNETS[1]} && ${SUBNETS[1]} =~ '.' ]] && SUBNET0=${SUBNETS[1]} || SUBNET1=${SUBNETS[1]}
      [[ ${RANGES[0]} =~ '.' ]] && RANGE0=${RANGES[0]} || RANGE1=${RANGES[0]}
      [[ -n ${RANGES[1]} && ${RANGES[1]} =~ '.' ]] && RANGE0=${RANGES[1]} || RANGE1=${RANGES[1]}
      [[ ${GATEWAYS[0]} =~ '.' ]] && GATEWAY0=${GATEWAYS[0]} || GATEWAY1=${GATEWAYS[0]}
      [[ -n ${GATEWAYS[1]} && ${GATEWAYS[1]} =~ '.' ]] && GATEWAY0=${GATEWAYS[1]} || GATEWAY1=${GATEWAYS[1]}
      [[ ${SERVERS[0]} =~ '.' ]] && SERVER0=${SERVERS[0]} || SERVER1=${SERVERS[0]}
      [[ -n ${SERVERS[1]} && ${SERVERS[1]} =~ '.' ]] && SERVER0=${SERVERS[1]} || SERVER1=${SERVERS[1]}
      # check for changes
      [[ $SUBNET != $SUBNET0 || $SUBNET6 != $SUBNET1 ]] && UPDATE=1
      [[ $RANGE != $RANGE0 ]] && UPDATE=1
      [[ (-n $GATEWAY && $GATEWAY != $GATEWAY0) || (-n $GATEWAY6 && $GATEWAY6 != $GATEWAY1) ]] && UPDATE=1
      [[ (-n $SERVER && $SERVER != $SERVER0) || (-n $SERVER6 && $SERVER6 != $SERVER1) ]] && UPDATE=1
      if [[ -z $UPDATE ]]; then
        # no changes, ignore
        SUBNET=; SUBNET6=;
      else
        # changed, remove first
        docker network rm $NETWORK >/dev/null
      fi
    fi
    # set parameters for custom network creation
    N4=$SUBNET;  R4=$RANGE;
    N6=$SUBNET6;
    [[ -n $SUBNET && -n $GATEWAY ]] && GATEWAY="--gateway=$GATEWAY" || GATEWAY=;
    [[ -n $SUBNET && -n $SERVER ]] && SERVER="--aux-address=server=$SERVER" || SERVER=;
    [[ -n $SUBNET && -n $RANGE ]] && RANGE="--ip-range=$RANGE" || RANGE=;
    [[ -n $SUBNET ]] && SUBNET="--subnet=$SUBNET"
    [[ -n $SUBNET6 && -n $GATEWAY6 ]] && GATEWAY6="--gateway=$GATEWAY6" || GATEWAY6=;
    [[ -n $SUBNET6 && -n $SERVER6 ]] && SERVER6="--aux-address=server6=$SERVER6" || SERVER6=;
    [[ -n $SUBNET6 ]] && SUBNET6="--ipv6 --subnet=$SUBNET6"
    if [[ -n $SUBNET || -n $SUBNET6 ]]; then
      TYPE=${NETWORK//[0-9.]/}
      driver $TYPE
      if [[ $TYPE == br ]]; then
        VHOST=$NETWORK
      elif [[ $TYPE == wlan ]]; then
        VHOST=$NETWORK
        ATTACH=ipvlan
      else
        [[ $DOCKER_ALLOW_ACCESS == yes && -n $IPV4 ]] && VHOST=vhost${NETWORK//[^0-9.]/} || VHOST=$NETWORK
      fi
      docker network create -d $ATTACH $SUBNET $GATEWAY $SERVER $RANGE $SUBNET6 $GATEWAY6 $SERVER6 -o parent=$VHOST $NETWORK | xargs docker network inspect -f "created network $ATTACH {{.Name}} with subnets: {{range .IPAM.Config}}{{.Subnet}}; {{end}}" 2>/dev/null | log
      # connect containers to this new network
      for CONNECT in ${NETRESTORE[$NETWORK]}; do
        CONTAINER=${CONNECT%,*}
        MY_TT=${CONNECT#*,}
        MY_IP=
        for IP in ${MY_TT//;/ }; do
          [[ $IP =~ ':' ]] && MY_IP="$MY_IP --ip6 $IP" || MY_IP="$MY_IP --ip $IP"
        done
        log "connecting $CONTAINER to network $NETWORK"
        docker network connect $MY_IP $NETWORK $CONTAINER >/dev/null
        if [[ -n ${CTRESTORE[$NETWORK]} ]]; then
          # rebuild the container to use changed network
          log "rebuild container $CONTAINER"
          /usr/local/emhttp/plugins/dynamix.docker.manager/scripts/rebuild_container $CONTAINER
        fi
      done
      # hack to let containers talk to host
      if [[ $TYPE == br ]]; then
        LINK=shim-$NETWORK
        GW=($(ip -4 route show default dev $NETWORK | awk '{print $3,$5;exit}'))
        if [[ $DOCKER_ALLOW_ACCESS == yes && -n $IPV4 ]]; then
          # create shim interface and copy parent IPv4 address to shim interface
          [[ -e $SYSTEM/$LINK ]] || run ip link add link $NETWORK name $LINK type $ATTACH mode $MODE
          run ip addr flush dev $LINK scope global
          run ip -4 addr add $IPV4 dev $LINK metric 0
          # disable IPv6 on shim interface
          echo 1 >$CONF6/$LINK/disable_ipv6
          run ip link set $LINK up
          if [[ -n $GW ]]; then
            if [[ -z ${GW[1]} ]]; then
              METRIC=1
              METRICS=$(ip -4 route show default | grep -Po 'metric \K\d+')
              while [[ " $METRICS " =~ " $METRIC " ]]; do ((METRIC++)); done
              # update existing route to avoid conflict with shim route
              run ip -4 route del default via $GW dev $NETWORK
              run ip -4 route add default via $GW dev $NETWORK metric $METRIC
            fi
            run ip -4 route add default via $GW dev $LINK metric 0
          fi
          log "created network $LINK for host access"
        elif [[ -e $SYSTEM/$LINK ]]; then
          # remove shim interface
          [[ -n $GW ]] && ip -4 route del default via $GW dev $LINK
          run ip addr flush dev $LINK
          run ip link set $LINK down
          run ip link del $LINK
        fi
      elif [[ $TYPE != wlan ]]; then
        if [[ $DOCKER_ALLOW_ACCESS == yes && -n $IPV4 ]]; then
          run ip addr flush dev $VHOST scope global
          # copy IPv4 address to vhost interface
          run ip -4 addr add $IPV4 dev $VHOST metric 0
          log "prepared network $VHOST for host access"
        else
          VHOST=vhost${NETWORK//[^0-9.]/}
          if [[ -e $SYSTEM/$VHOST ]]; then
            # remove IP addresses of vhost
            run ip addr flush dev $VHOST scope global
            # remove routing of vhost
            run ip -4 route flush dev $VHOST
            run ip -6 route flush dev $VHOST
          fi
        fi
      fi
    fi
  done
  # create IPv6 forward accept rule
  if [[ $IPV6_FORWARD == accept ]]; then
    log "creating forward accept rule for IPv6 network"
    ip6tables -P FORWARD ACCEPT
  fi
  log "Network started."
}

docker_network_stop(){
  log "Stopping network..."
  if ! docker_running; then return 1; fi
  for NIC in $NICS; do
    if [[ ${NIC:0:3} == eth ]]; then
      if [[ -e $SYSTEM/${NIC/eth/br} ]]; then
        NIC=${NIC/eth/br}
      elif [[ -e $SYSTEM/${NIC/eth/bond} ]]; then
        NIC=${NIC/eth/bond}
      fi
    fi
    driver ${NIC//[0-9]/}
    for NETWORK in $(network $ATTACH ${NIC//[^0-9]/}); do
      [[ $STOCK =~ ${NETWORK%%[0-9]*} || $DOCKER_USER_NETWORKS != preserve ]] && docker network rm $NETWORK >/dev/null
      TYPE=${NETWORK//[0-9.]/}
      if [[ $TYPE == br ]]; then
        LINK=shim-$NETWORK
        if [[ -e $SYSTEM/$LINK ]]; then
          GW=$(ip -4 route show default dev $LINK | awk '{print $3;exit}')
          [[ -n $GW ]] && run ip -4 route del default via $GW dev $LINK
          run ip addr flush dev $LINK
          run ip link set $LINK down
          run ip link del $LINK
        fi
      else
        VHOST=vhost${NETWORK//[^0-9.]/}
        [[ -e $SYSTEM/$VHOST ]] && run ip addr flush dev $VHOST
      fi
    done
  done
  log "Network stopped."
}

docker_container_start(){
  log "Starting containers..."
  local CONTAINER
  if ! docker_running; then return 1; fi
  if [[ -f $DOCKER_ROOT/unraid-autostart ]]; then
    while read -r CONTAINER; do
      CONTAINER=($CONTAINER)
      WAIT=${CONTAINER[1]}
      if container_exist $CONTAINER && ! container_running $CONTAINER && container_paths_exist $CONTAINER; then
        OUT=$(docker start $CONTAINER 2>&1)
        if  [[ $OUT =~ "Error:" ]]; then
          log "$CONTAINER: $OUT" &
        else
          run container_add_route $CONTAINER
          log "$CONTAINER: started successfully!" &
          if [[ $WAIT -gt 0 ]]; then
            log "$CONTAINER: wait $WAIT seconds" &
            sleep $WAIT
          fi
        fi
      fi
    done <$DOCKER_ROOT/unraid-autostart
  fi
  log "Containers started."
}

docker_container_stop(){
  log "Stopping containers..."
  if ! docker_running; then return 1; fi
  [[ -n $(running_containers) ]] && docker stop --time=${DOCKER_TIMEOUT:-10} $(running_containers) >/dev/null
  log "Unraid managed containers stopped."
}

docker_service_start(){
  log "Starting $DAEMON..."
  local REPLY
  [[ -x $DOCKER ]] && REPLY= || REPLY="Failed"
  if [[ -z $REPLY ]]; then
    if ! mountpoint $DOCKER_ROOT &>/dev/null; then
      REPLY="No image mounted at $DOCKER_ROOT"
    elif docker_running; then
      REPLY="Already started"
    fi
  fi
  if [[ -z $REPLY ]]; then
    # If there is an old PID file (no docker running), clean it up:
    if [[ -r $DOCKER_PIDFILE ]]; then
      if ! docker_running; then
        rm -f $DOCKER_PIDFILE
      fi
    fi
    nohup $UNSHARE --propagation slave -- $DOCKER -p $DOCKER_PIDFILE $DOCKER_OPTS >>$DOCKER_LOG 2>&1 &
    wait_daemon
    if docker_running; then REPLY="Started"; else REPLY="Failed"; fi
  fi
  log "$DAEMON...  $REPLY."
}

docker_service_stop(){
  log "Stopping $DAEMON..."
  local REPLY
  # If there is no PID file, ignore this request...
  if [[ -r $DOCKER_PIDFILE ]]; then
    # Try to stop dockerd gracefully
    kill $(docker_pid) 2>/dev/null
    # show waiting message
    echo "Waiting 30 seconds for $DAEMON to die."
    TIMER=30
    # must ensure daemon has exited
    while [[ $TIMER -gt 0 ]]; do
      sleep 1
      if [[ $(ps -p $(docker_pid) -o comm= 2>/dev/null) != $DOCKERD ]]; then
        rm -f $DOCKER_PIDFILE
        # tear down the bridge
        if [[ -e $SYSTEM/docker0 ]]; then
          run ip link set docker0 down
          run ip link del docker0
        fi
        REPLY="Stopped"
        # signal successful stop
        TIMER=-1
      else
        ((TIMER--))
      fi
    done
    if [[ $TIMER -eq 0 ]]; then
      log "Error: process will not die!"
      # Send SIGKILL to dockerd
      kill -SIGKILL $(docker_pid) 2>/dev/null
      # Remove .sock and .pid
      rm -f /var/run/docker.sock $DOCKER_PIDFILE
      REPLY="Killed"
    fi
  else
    REPLY="Already stopped"
  fi
  log "$DAEMON...  $REPLY."
}

docker_status(){
  if docker_running; then
    echo "$DAEMON is currently running."
  else
    echo "$DAEMON is not running."
    exit 1
  fi
}

case "$1" in
'start')
  docker_service_start
  docker_network_start
  docker_container_start &>/dev/null &
  disown
  ;;
'stop')
  docker_container_stop
  docker_network_stop
  docker_service_stop
  ;;
'force_stop')
  docker_container_stop
  docker_service_stop
  ;;
'restart')
  docker_container_stop
  docker_network_stop
  docker_service_stop
  sleep 1
  docker_service_start
  docker_network_start
  docker_container_start &>/dev/null &
  disown
  ;;
'status')
  docker_status
  ;;
*)
  echo "Usage: $BASENAME start|stop|force_stop|restart|status"
  exit 1
esac
exit 0
